package com.haircut.project.service.salary.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.haircut.project.constant.RecordConstant;
import com.haircut.project.dao.record.ProjectRecordMapper;
import com.haircut.project.dao.salary.UserSalaryBatchMapper;
import com.haircut.project.dao.salary.UserSalaryMapper;
import com.haircut.project.model.record.ProjectRecord;
import com.haircut.project.model.salary.UserSalary;
import com.haircut.project.model.salary.UserSalaryBatch;
import com.haircut.project.service.salary.ISalaryCalcService;
import com.haircut.project.vo.record.SalaryQueryVo;
import com.haircut.sys.dao.dept.SyDeptMapper;
import com.haircut.sys.dao.user.SysUserMapper;
import com.haircut.sys.model.dept.SysDept;
import com.haircut.sys.vo.user.UserReqVo;
import com.haircut.sys.vo.user.UserResVo;
import com.modular.constant.BaseCodeDesc;
import com.modular.constant.NumberConstant;
import com.modular.exception.ModularException;
import com.modular.redis.RedisUtils;
import com.modular.result.PageParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Semaphore;

@Service
public class SalaryCalcServiceImpl implements ISalaryCalcService {
    @Autowired
    private SyDeptMapper syDeptMapper;
    @Autowired
    private UserSalaryMapper userSalaryMapper;
    @Autowired
    private ProjectRecordMapper projectRecordMapper;
    @Autowired
    private UserSalaryBatchMapper userSalaryBatchMapper;
    @Autowired
    private SysUserMapper sysUserMapper;
    /**
     * 异步处理，每次只处理10条
     */
    private final Semaphore semaphore = new Semaphore(10);
    @Autowired
    @Qualifier("modularSnowflake")
    private Snowflake snowflake;
    @Autowired
    private RedisUtils redisUtils;
    int pageSize = NumberConstant.HUNDRED * NumberConstant.NINE;
    private final Logger logger = LoggerFactory.getLogger(SalaryCalcServiceImpl.class);

    /**
     *  判断是否可以运行
     * @return
     */
    private boolean hasRun(){
        Date currDate = DateUtil.date();
        int day = DateUtil.dayOfMonth(currDate);
        if(ObjectUtil.equal(day,NumberConstant.ONE) || ObjectUtil.equal(day,NumberConstant.TEN + NumberConstant.SIX)){
            return Boolean.TRUE;
        }
        return  Boolean.FALSE;
    }

    /**
     * 就算工资
     */
    @Override
    public void calcSalary(String redisKey,Boolean ignoreDate,Date staticDate)
    {
        if(!ignoreDate && !hasRun()){
            return ;
        }
        boolean hasNext = Boolean.TRUE;
        int currPage = NumberConstant.ZERO;
        IPage<?> pageReq = null;
        Page<SysDept> deptPage = null;
        PageParams reqPage = new PageParams();
        reqPage.setPageSize(pageSize);
        String batchNo = DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_PATTERN);;
        while(hasNext){
            redisUtils.flushRedis(redisKey);
            currPage ++;
            reqPage.setPageNum(currPage);
            pageReq = Page.of(currPage,pageSize,Boolean.FALSE);
            deptPage = syDeptMapper.findCalcDeptPage(pageReq,reqPage);
            if(ObjectUtil.isNull(deptPage) || CollUtil.isEmpty(deptPage.getRecords())){
                break;
            }
            if(deptPage.getRecords().size() < currPage){
                hasNext = Boolean.FALSE;
            }
            deptPage.getRecords().forEach(p->{
                logger.info("salar batch deptId {},deptName {} str",p.getId(),p.getDeptName());
                long s = System.currentTimeMillis();
                try{
                    semaphore.acquire();
                    calcDeptBatchSalary(p,redisKey,staticDate,batchNo);
                }catch (Exception e){
                    logger.error(StrUtil.format("salar batch calc err deptId {} deptName {}",p.getId(),p.getDeptName()),e);
                }finally {
                    semaphore.release();
                    logger.info("salar batch deptId {},deptName {} time {}",p.getId(),p.getDeptName(),(System.currentTimeMillis()-s));
                }
            });
        }
    }
    

    /**
     * 根据部门计算工资
     * @param dept
     * @param redisKey
     */
    private void calcDeptBatchSalary(SysDept dept,String redisKey,Date yesterday,String batchNo) {
        redisUtils.flushRedis(redisKey);
        UserSalaryBatch salaryBatch = userSalaryBatchMapper.findLastByDeptId(dept.getId());
        SalaryQueryVo queryVo = new SalaryQueryVo();
        queryVo.setDeptId(dept.getId());
        queryVo.setEndDate(DateUtil.endOfDay(yesterday));
        boolean hasAdd = Boolean.TRUE;
        if (ObjectUtil.isNotNull(salaryBatch)) {
            // 如果生成类型为已经生成，如果单号和生成类型为
            if (ObjectUtil.equal(batchNo, salaryBatch.getSalaryNo())) {
                if (ObjectUtil.equal(salaryBatch.getGenType(), NumberConstant.TWO)) {
                    return;
                }
                // 没有生成成功，则需要重新生成
                queryVo.setStartDate(salaryBatch.getStartTime());
                queryVo.setEndDate(salaryBatch.getEndTime());
                hasAdd = Boolean.FALSE;
            } else {
                Date startTime = salaryBatch.getEndTime();
                queryVo.setStartDate(DateUtil.beginOfDay(startTime));
            }
        }
        if(hasAdd){
            Date currDate = DateUtil.date();
            salaryBatch = new UserSalaryBatch();
            salaryBatch.setId(snowflake.nextIdStr());
            salaryBatch.setStartTime(queryVo.getStartDate());
            salaryBatch.setEndTime(yesterday);
            salaryBatch.setGenType(NumberConstant.ONE);
            salaryBatch.setSalaryNo(batchNo);
            salaryBatch.setSalaryType(RecordConstant.SalaryType.NEW_RECORD);
            salaryBatch.setDeptId(dept.getId());
            salaryBatch.setCreateDate(currDate);
            salaryBatch.setModifyDate(currDate);
            userSalaryBatchMapper.insert(salaryBatch);
        }
        // 计算批次下所有的用户信息
        int deptUerNum = calcDeptUser(queryVo,salaryBatch,redisKey);
        if(deptUerNum  > NumberConstant.ZERO) {
            LambdaUpdateWrapper<UserSalaryBatch> modify = Wrappers.lambdaUpdate();
            modify.set(UserSalaryBatch::getGenType, NumberConstant.TWO);
            modify.set(UserSalaryBatch::getModifyDate, DateUtil.date());
            modify.eq(UserSalaryBatch::getId, salaryBatch.getId());
            userSalaryBatchMapper.update(modify);
        }else if(hasAdd){
            userSalaryBatchMapper.deleteById(salaryBatch.getId());
        }
    }

    /**
     * 计算部门用户
     * @param queryVo
     * @param batch
     * @param redisKey
     */
    private int calcDeptUser(SalaryQueryVo queryVo,UserSalaryBatch batch,String redisKey){
        UserReqVo reqVo = new UserReqVo();
        reqVo.setDeptId(queryVo.getDeptId());
        IPage<?> pageReq = null;
        Page<UserResVo> userPage = null;
        boolean hasNext = Boolean.TRUE;
        int currPage = NumberConstant.ZERO,userNum = NumberConstant.ZERO;
        ISalaryCalcService salaryService = SpringUtil.getBean(ISalaryCalcService.class);
        while (hasNext){
            redisUtils.flushRedis(redisKey);
             currPage ++;
            pageReq = Page.of(currPage,pageSize,Boolean.FALSE);
            userPage = sysUserMapper.findPage(pageReq,reqVo);
            if(ObjectUtil.isNull(userPage) || CollUtil.isEmpty(userPage.getRecords())){
                break;
            }
            if(userPage.getRecords().size() < pageSize){
                hasNext = Boolean.FALSE;
            }
            for(UserResVo resVo:userPage.getRecords()){
                queryVo.setUserId(resVo.getId());
                Integer recordNum  = salaryService.calcUserSalary(queryVo,batch,redisKey);
                if(recordNum > NumberConstant.ZERO){
                    userNum ++;
                }
            }
        }
        return userNum;

    }


    /**
     * 添加批次下所有用户的工资信息
     * @param queryVo
     * @param batch
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer calcUserSalary(SalaryQueryVo queryVo,UserSalaryBatch batch,String redisKey){
        // 如果已经生成，则不处理
        UserSalary salary = findUserSalary(batch.getId(),queryVo.getUserId());
        if(ObjectUtil.isNotNull(salary)){
            return NumberConstant.ZERO;
        }
        salary = initSalary(queryVo.getUserId(),queryVo.getDeptId());
        salary.setBatchId(batch.getId());
        int recordNum = calcUserSalary(queryVo,redisKey,salary);
        if(recordNum > NumberConstant.ZERO) {
            userSalaryMapper.insert(salary);
        }
        return recordNum;
    }

    /**
     * 计算工资
     * @param queryVo
     * @param redisKey
     * @param salary
     * @return
     */
    private int calcUserSalary(SalaryQueryVo queryVo,String redisKey,UserSalary salary){
        IPage<?> reqPage = null;
        boolean hasNext = Boolean.TRUE;
        int currPage = NumberConstant.ZERO,recordNum = NumberConstant.ZERO;
        List<ProjectRecord> recordList = null;
        List<String> recordIdList = null;
        LambdaUpdateWrapper<ProjectRecord> modify = null;
        while(hasNext){
            redisUtils.flushRedis(redisKey);
            currPage ++;
            reqPage = Page.of(currPage,pageSize,Boolean.FALSE);
            recordList = projectRecordMapper.findSalaryCalsPage(reqPage,queryVo);
            if(ObjectUtil.isNull(recordList) || CollUtil.isEmpty(recordList)){
                break;
            }
            if(recordList.size() < pageSize){
                hasNext = Boolean.FALSE;
            }
            recordNum ++;
            calcUserSalary(recordList,salary);
            recordIdList = recordList.stream().map(ProjectRecord::getId).toList();
            modify = Wrappers.lambdaUpdate();
            modify.set(ProjectRecord::getFinanceId,salary.getId());
            modify.in(ProjectRecord::getId,recordIdList);
            projectRecordMapper.update(modify);
        }

        return recordNum;
    }

    /**
     * 计算工资
     * @param recordList
     * @param salary
     */
    private void calcUserSalary(List<ProjectRecord> recordList, UserSalary salary){
        for(ProjectRecord record:recordList){
            salary.setTotalAmount(salary.getTotalAmount().add(record.getTotalAmount()));
            salary.setCompanyCashAmount(salary.getCompanyCashAmount().add(record.getCompanyCashAmount()));
            salary.setCompanyCardAmount(salary.getCompanyCardAmount().add(record.getCompanyCardAmount()));

            salary.setTotalCashAmount(salary.getTotalCashAmount().add(record.getTotalCashAmount()));
            salary.setTotalCardAmount(salary.getTotalCardAmount().add(record.getTotalCardAmount()));
            salary.setTechCardAmount(salary.getTechCardAmount().add(record.getTechCardAmount()));
            salary.setTechCashAmount(salary.getTechCashAmount().add(record.getTechCashAmount()));

            salary.setTipsCardAmount(salary.getTipsCardAmount().add(record.getTipsCardAmount()));
            salary.setDiscountAmount(salary.getDiscountAmount().add(record.getDiscountAmount()));
            if(ObjectUtil.equal(record.getRecordType(), RecordConstant.RecordType.NEW_RECORD)){
                salary.setSalaryType(RecordConstant.SalaryType.ERR_INIT);
                salary.setErrReason("有未确认的记录");
            }
        }
    }

    /**
     * 根据批次id和用户id查询工资喜喜
     * @param batchId
     * @param userId
     * @return
     */
    private UserSalary findUserSalary(String batchId,String userId){
        LambdaQueryWrapper<UserSalary> query = Wrappers.lambdaQuery();
        query.eq(UserSalary::getBatchId,batchId).eq(UserSalary::getUserId,userId);
        return userSalaryMapper.selectOne(query);
    }

    /**
     * 初始化工资
     */
    private UserSalary initSalary(String userId,String deptId){
        UserSalary salary = new UserSalary();
        Date currDate = DateUtil.date();
        salary.setId(snowflake.nextIdStr());
        salary.setTotalAmount(BigDecimal.ZERO);
        salary.setCompanyCashAmount(BigDecimal.ZERO);
        salary.setCompanyCardAmount(BigDecimal.ZERO);
        salary.setTotalCashAmount(BigDecimal.ZERO);
        salary.setTotalCardAmount(BigDecimal.ZERO);
        salary.setTechCardAmount(BigDecimal.ZERO);
        salary.setTechCashAmount(BigDecimal.ZERO);
        salary.setTipsCardAmount(BigDecimal.ZERO);
        salary.setDiscountAmount(BigDecimal.ZERO);
        salary.setCreateDate(currDate);
        salary.setSalaryType(RecordConstant.SalaryType.NEW_RECORD);
        salary.setModifyDate(currDate);
        salary.setUserId(userId);
        salary.setSalaryNo(DateUtil.format(currDate, DatePattern.PURE_DATE_PATTERN));
        salary.setDeptId(deptId);
        return salary;
    }

    /**
     * 重新计算
     * @param salary
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void recalculation( UserSalary salary,String redisKey)
    {
        UserSalaryBatch salaryBatch = userSalaryBatchMapper.selectById(salary.getBatchId());
        LambdaUpdateWrapper<ProjectRecord> modify = Wrappers.lambdaUpdate();
        modify.eq(ProjectRecord::getFinanceId,null);
        modify.set(ProjectRecord::getFinanceId,salary.getId());
        projectRecordMapper.update(modify);

        salary.setSalaryType(RecordConstant.SalaryType.NEW_RECORD);
        salary.setErrReason(StrUtil.EMPTY);

        SalaryQueryVo queryVo = new SalaryQueryVo();
        queryVo.setUserId(salary.getUserId());
        queryVo.setDeptId(salaryBatch.getDeptId());
        queryVo.setEndDate(salaryBatch.getEndTime());
        queryVo.setStartDate(salaryBatch.getStartTime());
        calcUserSalary(queryVo,redisKey,salary);
        userSalaryMapper.updateById(salary);

    }
}
